﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ServerNodeLib.DataAdapter
{
	/// <summary>
	/// The purpose of this class is to separate the persistence logic
	/// from the data structures themselves.
	/// 
	/// I decided not to add individual serialization methods to Batch,
	/// BatchGroup, etc because long-tem we may move the persistence
	/// to a different db or format.  I want to insulate the RoundRobin
	/// code and the data structure code from that change.
	/// </summary>
	public class SqlCeDataAdapter : INodeDataAdapter, IBatchDataAdapter, IBatchGroupDataAdapter
	{
		//public static SqlCeDataAdapter Instance = new SqlCeDataAdapter();

		private BatchStoreDataSetTableAdapters.BatchTableAdapter batchTA;
		private BatchStoreDataSetTableAdapters.BatchCommandTableAdapter batchComTA;
		private BatchStoreDataSetTableAdapters.BatchResourceTableAdapter batchResTA;
		private BatchStoreDataSetTableAdapters.NodeTableAdapter nodeTA;
		private BatchStoreDataSetTableAdapters.NodeActionRequestTableAdapter nodeActTA;
		private BatchStoreDataSetTableAdapters.BatchGroupTableAdapter bgroupTA;

		public SqlCeDataAdapter()
		{
			batchTA = new ServerNodeLib.BatchStoreDataSetTableAdapters.BatchTableAdapter();
			batchComTA = new ServerNodeLib.BatchStoreDataSetTableAdapters.BatchCommandTableAdapter();
			batchResTA = new ServerNodeLib.BatchStoreDataSetTableAdapters.BatchResourceTableAdapter();
			nodeTA = new ServerNodeLib.BatchStoreDataSetTableAdapters.NodeTableAdapter();
			nodeActTA = new ServerNodeLib.BatchStoreDataSetTableAdapters.NodeActionRequestTableAdapter();
			bgroupTA = new ServerNodeLib.BatchStoreDataSetTableAdapters.BatchGroupTableAdapter();
		}

		~SqlCeDataAdapter()
		{
			batchTA.Connection.Close();
			batchComTA.Connection.Close();
			batchResTA.Connection.Close();
			nodeTA.Connection.Close();
			nodeActTA.Connection.Close();
			bgroupTA.Connection.Close();
		}

		public BatchStoreDataSetTableAdapters.BatchTableAdapter Batches
		{
			get
			{
				return this.batchTA;
			}
		}

		public BatchStoreDataSetTableAdapters.NodeTableAdapter Nodes
		{
			get
			{
				return this.nodeTA;
			}
		}

		#region INodeDataAdapter Members

		private Node ConstructNode(BatchStoreDataSet.NodeRow row)
		{
			Node n = new Node();
			n.AcceptNewJobs = row.AcceptNewJobs;
			//n.BatchId = row.b
			n.EndTime = row.EndTime;
			n.ErrorTimeout = row.ErrorTimeout;
			n.IpAddress = row.IpAddress;
			n.LastSeen = row.LastSeen;
			n.NodeId = row.NodeId;
			n.NodeName = row.NodeName;
			n.NormalTimeout = row.NormalTimeout;
			n.PercentComplete = row.PercentComplete;
			n.StartTime = row.StartTime;
			n.State = (NodeState)row.State;
			n.StatusDescription = row.StatusDescription;
			//n.OnLoadActionRequests += new LoadActionRequests(this.LoadActionRequests);
			return n;
		}

		public Node GetNode(Guid nodeId)
		{
			lock (nodeTA)
			{
				BatchStoreDataSet.NodeDataTable ndt = nodeTA.GetDataByNodeId(nodeId);
				if (ndt.Count > 0)
				{
					return ConstructNode(ndt[0]);
				}
				else
				{
					return null;
				}
			}
		}

		public List<Node> GetNodes(NodeState state)
		{
			lock (nodeTA)
			{
				List<Node> retList = new List<Node>();
				BatchStoreDataSet.NodeDataTable ndt = nodeTA.GetDataByState((int)state);
				foreach (BatchStoreDataSet.NodeRow row in ndt)
				{
					retList.Add(ConstructNode(row));
				}
				return retList;
			}
		}

		public List<Node> GetNodes(DateTime minStartTime, DateTime maxStartTime)
		{
			throw new NotImplementedException();
		}

		public List<Node> GetNodes(DateTime maxLastSeen)
		{
			lock (nodeTA)
			{
				List<Node> retList = new List<Node>();
				BatchStoreDataSet.NodeDataTable ndt = nodeTA.GetDataByMaxLastSeen(maxLastSeen);
				foreach (BatchStoreDataSet.NodeRow row in ndt)
				{
					retList.Add(ConstructNode(row));
				}
				return retList;
			}
		}

		public int NumberNodes(NodeState state)
		{
			lock (nodeTA)
			{
				int? i = nodeTA.CountByState((int)state);
				if (i.HasValue)
					return i.Value;
				else
					return 0;
			}
		}

		public int NumberNodes()
		{
			lock (nodeTA)
			{
				int? i = nodeTA.CountNodes();
				if (i.HasValue)
					return i.Value;
				else
					return 0;
			}
		}

		public bool SaveNode(Node node)
		{
			lock (nodeTA)
			{
				// see if node already exists, if so update, else insert
				Node existingNode = GetNode(node.NodeId);
				if (existingNode == null)
				{
					int result = nodeTA.InsertNamed(node.NodeId, node.LastSeen, (int)node.State,
						node.StatusDescription, node.PercentComplete, node.StartTime,
						node.EndTime, node.IpAddress, node.NodeName, node.AcceptNewJobs,
						node.ErrorTimeout, node.NormalTimeout);

					foreach (ActionRequest ar in node.ActionRequests)
					{
						result += nodeActTA.InsertNamed(node.NodeId, (int)ar.ActionType, ar.PayloadKey, ar.PayloadValue, ar.AddTime, ar.ActionReqId);
					}
					return result > 0;
				}
				else
				{
					int result = nodeTA.UpdateNamed(node.NodeId, node.LastSeen, (int)node.State, node.StatusDescription, node.PercentComplete, node.StartTime,
						node.EndTime, node.IpAddress, node.NodeName, node.AcceptNewJobs, node.ErrorTimeout, node.NormalTimeout);

					lock (nodeActTA)
					{
						foreach (ActionRequest ar in node.ActionRequests)
						{
							BatchStoreDataSet.NodeActionRequestDataTable adt = nodeActTA.GetDataByActionReqId(ar.ActionReqId);
							if (adt.Count > 0)
							{
								result += nodeActTA.UpdateNamed(node.NodeId, (int)ar.ActionType, ar.PayloadKey, ar.PayloadValue, ar.AddTime, ar.ActionReqId);
							}
							else
							{
								result += nodeActTA.InsertNamed(node.NodeId, (int)ar.ActionType, ar.PayloadKey, ar.PayloadValue, ar.AddTime, ar.ActionReqId);
							}
						}
					}
					return result > 0;
				}
			}
		}

		public bool DeleteNode(Guid nodeId)
		{
			lock (nodeTA)
			{
				return nodeTA.Delete(nodeId) > 0;
			}
		}

		public List<ActionRequest> LoadActionRequests(Guid nodeId)
		{
			lock (nodeActTA)
			{
				List<ActionRequest> retList = new List<ActionRequest>();
				BatchStoreDataSet.NodeActionRequestDataTable ndt = nodeActTA.GetDataByNodeId(nodeId);
				foreach (BatchStoreDataSet.NodeActionRequestRow row in ndt)
				{
					// construct right here... no need for a separate method
					ActionRequest aq = new ActionRequest();
					aq.ActionType = (ActionRequestType)row.ActionType;
					aq.PayloadKey = row.PayloadKey;
					aq.PayloadValue = row.PayloadValue;
					aq.ActionReqId = row.ActionReqId;
					aq.AddTime = row.TimeAdded;
					retList.Add(aq);
				}
				return retList;
			}
		}

		#endregion

		#region IBatchDataAdapter Members
		private Batch ConstructBatch(BatchStoreDataSet.BatchRow row)
		{
			Batch b = new Batch();
			//b.OnLoadBatchCommands += new LoadBatchCommands(this.LoadBatchCommands);
			//b.OnLoadBatchResources += new LoadBatchResources(this.LoadBatchResources);
			//b.OnLoadReturnResources += new LoadReturnResources(this.LoadReturnResources);
			b.BatchGroup = row.BatchGroup;
			b.BatchId = row.BatchId;
			b.Comments = row.Comments;
			b.LogFilenameFormat = row.LogFilenameFormat;
			b.NodeBegin = row.NodeBegin;
			b.NodeEnd = row.NodeEnd;
			b.NodeId = row.NodeId;
			b.Result = null;
			b.Status = (BatchStatus)row.Status;
			return b;
		}

		private BatchCommand ConstructBatchCommand(BatchStoreDataSet.BatchCommandRow row)
		{
			BatchCommand bc = new BatchCommand();
			bc.Arguments = row.Arguments;
			bc.BatchCommandId = row.BatchComId;
			bc.BreakOnException = row.BreakOnException;
			bc.CaptureOutput = row.CaptureOutput;
			bc.Command = row.Command;
			bc.ErrorOnTimeOut = row.ErrorOnTimeOut;
			bc.MaxTimeSeconds = row.MaxTimeSeconds;
			bc.MinTimeSeconds = row.MinTimeSeconds;
			bc.Path = row.Path;
			return bc;
		}

		private BatchResource ConstructBatchResource(BatchStoreDataSet.BatchResourceRow row)
		{
			BatchResource br = new BatchResource();
			br.BatchResId = row.BatchResId;
			br.ClientPath = row.ClientPath;
			br.ServerPath = row.ServerPath;
			return br;
		}

		public Batch GetBatch(Guid batchId)
		{
			lock (batchTA)
			{
				BatchStoreDataSet.BatchDataTable bdt = batchTA.GetDataByBatchId(batchId);
				if (bdt.Count > 0)
				{
					return ConstructBatch(bdt[0]);
				}
				else
				{
					return null;
				}
			}
		}

		public Batch GetBatchOnNode(Guid nodeId)
		{
			lock (batchTA)
			{
				BatchStoreDataSet.BatchDataTable bdt = batchTA.GetDataByNodeId(nodeId);
				if (bdt.Count > 0)
				{
					return ConstructBatch(bdt[0]);
				}
				else
				{
					return null;
				}
			}
		}

		public List<Batch> GetBatches(BatchStatus status)
		{
			return GetBatches(status, int.MaxValue);
		}

		public int NumberBatches(BatchStatus status)
		{
			lock (batchTA)
			{
				int? i = batchTA.CountBatchesByStatus((int)status);
				if (i.HasValue)
					return i.Value;
				else
					return 0;
			}
		}

		public int NumberBatches()
		{
			lock (batchTA)
			{
				int? i = batchTA.CountBatches();
				if (i.HasValue)
					return i.Value;
				else
					return 0;
			}
		}

		public List<Batch> GetBatches(BatchStatus status, int limit)
		{
			lock (batchTA)
			{
				if (limit <= 0)
					limit = int.MaxValue;
				// as far as I can tell, the limit operator is not available in SqlCE
				int count = 0;
				List<Batch> retList = new List<Batch>();
				BatchStoreDataSet.BatchDataTable bdt = batchTA.GetDataByStatus((int)status);
				foreach (BatchStoreDataSet.BatchRow row in bdt)
				{
					if (count >= limit)
						break;
					count++;
					retList.Add(ConstructBatch(row));
				}
				return retList;
			}
		}

		public List<Batch> GetBatchesInGroup(Guid groupId)
		{
			lock (batchTA)
			{
				List<Batch> retList = new List<Batch>();
				BatchStoreDataSet.BatchDataTable bdt = batchTA.GetDataByBatchGroup(groupId);
				foreach (BatchStoreDataSet.BatchRow row in bdt)
				{
					retList.Add(ConstructBatch(row));
				}
				return retList;
			}
		}

		public bool SaveBatch(Batch batch)
		{
			lock (batchTA)
			{
				// see if node already exists, if so update, else insert
				Batch existingBatch = GetBatch(batch.BatchId);
				if (existingBatch == null)
				{
					int result = batchTA.InsertNamed(batch.BatchId, batch.BatchGroup,
						batch.Comments, batch.NodeId,
						batch.QueueAdd, batch.NodeBegin, batch.NodeEnd,
						batch.LogFilenameFormat, (int)batch.Status);

					foreach (BatchResource br in batch.BatchResources)
					{
						result += batchResTA.InsertNamed(batch.BatchId, br.ClientPath, br.ServerPath, false, br.BatchResId);
					}

					foreach (BatchResource br in batch.ReturnResources)
					{
						result += batchResTA.InsertNamed(batch.BatchId, br.ClientPath, br.ServerPath, true, br.BatchResId);
					}

					foreach (BatchCommand br in batch.BatchCommands)
					{
						result += batchComTA.InsertNamed(batch.BatchId, br.Arguments, br.Command, br.Path, br.ErrorOnTimeOut,
							br.BreakOnException, br.CaptureOutput, br.MaxTimeSeconds, br.MinTimeSeconds, br.BatchCommandId);
					}
					return result > 0;
				}
				else
				{
					int result = batchTA.UpdateNamed(batch.BatchId, batch.BatchGroup,
						batch.Comments, batch.NodeId,
						batch.QueueAdd, batch.NodeBegin, batch.NodeEnd,
						batch.LogFilenameFormat, (int)batch.Status);
					foreach (BatchResource br in batch.BatchResources)
					{
						BatchStoreDataSet.BatchResourceDataTable adt = batchResTA.GetDataByBatchResId(br.BatchResId);
						if (adt.Count > 0)
						{
							result += batchResTA.UpdateNamed(batch.BatchId, br.ClientPath, br.ServerPath, false, br.BatchResId);
						}
						else
						{
							result += batchResTA.InsertNamed(batch.BatchId, br.ClientPath, br.ServerPath, false, br.BatchResId);
						}
					}

					foreach (BatchResource br in batch.ReturnResources)
					{
						BatchStoreDataSet.BatchResourceDataTable adt = batchResTA.GetDataByBatchResId(br.BatchResId);
						if (adt.Count > 0)
						{
							result += batchResTA.UpdateNamed(batch.BatchId, br.ClientPath, br.ServerPath, true, br.BatchResId);
						}
						else
						{
							result += batchResTA.InsertNamed(batch.BatchId, br.ClientPath, br.ServerPath, true, br.BatchResId);
						}
					}

					foreach (BatchCommand br in batch.BatchCommands)
					{
						BatchStoreDataSet.BatchCommandDataTable adt = batchComTA.GetDataByBatchComId(br.BatchCommandId);
						if (adt.Count > 0)
						{
							result += batchComTA.UpdateNamed(batch.BatchId, br.Arguments, br.Command, br.Path, br.ErrorOnTimeOut,
							br.BreakOnException, br.CaptureOutput, br.MaxTimeSeconds, br.MinTimeSeconds, br.BatchCommandId);
						}
						else
						{
							result += batchComTA.InsertNamed(batch.BatchId, br.Arguments, br.Command, br.Path, br.ErrorOnTimeOut,
							br.BreakOnException, br.CaptureOutput, br.MaxTimeSeconds, br.MinTimeSeconds, br.BatchCommandId);
						}
					}
					return result > 0;
				}
			}
		}

		public bool DeleteBatch(Guid batchId)
		{
			lock (batchTA)
			{
				int result = 0;
				result += batchTA.Delete(batchId);
				// the following should be covered from cascade in foreign key constraint
				//result += batchResTA.DeleteByBatchId(batchId);
				//result += batchComTA.DeleteByBatchId(batchId);
				return result > 0;
			}
		}

		public BatchResource GetBatchResource(Guid BatchId, String clientName)
		{
			lock (batchResTA)
			{
				BatchStoreDataSet.BatchResourceDataTable bdt = batchResTA.GetDataByBatchIdAndClientPath(BatchId, clientName);
				foreach (BatchStoreDataSet.BatchResourceRow row in bdt)
				{
					return ConstructBatchResource(row);
				}
				return null;
			}
		}

		public List<BatchCommand> LoadBatchCommands(Guid BatchId)
		{
			lock (batchComTA)
			{
				List<BatchCommand> retList = new List<BatchCommand>();
				BatchStoreDataSet.BatchCommandDataTable bdt = batchComTA.GetDataByBatchId(BatchId);
				foreach (BatchStoreDataSet.BatchCommandRow row in bdt)
				{
					retList.Add(ConstructBatchCommand(row));
				}
				return retList;
			}
		}

		public List<BatchResource> LoadBatchResources(Guid BatchId)
		{
			lock (batchResTA)
			{
				List<BatchResource> retList = new List<BatchResource>();
				BatchStoreDataSet.BatchResourceDataTable bdt = batchResTA.GetDataByBatchId(BatchId, false);
				foreach (BatchStoreDataSet.BatchResourceRow row in bdt)
				{
					retList.Add(ConstructBatchResource(row));
				}
				return retList;
			}
		}

		public List<BatchResource> LoadReturnResources(Guid BatchId)
		{
			lock (batchResTA)
			{
				List<BatchResource> retList = new List<BatchResource>();
				BatchStoreDataSet.BatchResourceDataTable bdt = batchResTA.GetDataByBatchId(BatchId, true);
				foreach (BatchStoreDataSet.BatchResourceRow row in bdt)
				{
					retList.Add(ConstructBatchResource(row));
				}
				return retList;
			}
		}

		#endregion

		#region IBatchGroupDataAdapter Members
		private BatchGroup ConstructBatchGroup(BatchStoreDataSet.BatchGroupRow row)
		{
			BatchGroup bg = new BatchGroup();
			bg.Comment = row.Comment;
			bg.CompleteBatches = row.CompleteBatches;
			bg.EndTime = row.EndTime;
			bg.GroupId = row.GroupId;
			bg.RemainingBatches = row.RemainingBatches;
			bg.StartTime = row.StartTime;
			bg.TotalBatches = row.TotalBatches;
			bg.State = (BatchStatus)row.Status;
			bg.OutputDirectory = row.OutputDirectory;
			return bg;
		}

		public BatchGroup GetBatchGroup(Guid GroupId)
		{
			lock (bgroupTA)
			{
				BatchStoreDataSet.BatchGroupDataTable ndt = bgroupTA.GetDataByGroupId(GroupId);
				if (ndt.Count > 0)
				{
					return ConstructBatchGroup(ndt[0]);
				}
				else
				{
					return null;
				}
			}
		}

		public bool SaveBatchGroup(BatchGroup group)
		{
			lock (bgroupTA)
			{
				if (group == null)
					return false;
				BatchGroup existing = GetBatchGroup(group.GroupId);
				if (existing == null)
				{
					return bgroupTA.InsertNamed(group.GroupId, group.StartTime, group.EndTime, group.Comment, group.TotalBatches, group.CompleteBatches,
						group.OutputDirectory, (int)group.State, group.RemainingBatches) > 0;
				}
				else
				{
					return bgroupTA.UpdateNamed(group.GroupId, group.StartTime, group.EndTime, group.Comment, group.TotalBatches, group.CompleteBatches,
						group.OutputDirectory, (int)group.State, group.RemainingBatches) > 0;
				}
			}
		}

		public bool DeleteBatchGroup(Guid groupId)
		{
			lock (bgroupTA)
			{
				// cascading is in the data set definition, so omitted here
				return bgroupTA.Delete(groupId) > 0;
			}
		}

		#endregion
	}
}
